#include <GL/glut.h>
#include <stdlib.h>

#define MUNDO 10.0f
#define PI 3.141592654
/* Variables globales que guardan el tope de las coordenadas de mundo */
float xMax, yMax;
/* Variable global que controla la proyeccion, 0 - ortogonal, 1 perspectiva */
int projection;
/* Variable global para guardar el aspect */
double aspect;
/*Variables globales para la trasnformacion de vision */
double phi, theta;
/*Variable global para pintar los puntos de control */
int puntosControl;
/*Variable global para pintar los puntos de control */
int solid;


void inicializar (void);
void redimensiona (int alto, int ancho);
void dibuja (void);
void teclado (unsigned char key, int cx, int cy);
void tecladoEspecial (int key, int cy, int cx);
void animacion (void);
void raton (int boton, int estado, int cx, int cy);
void setProjection(void);

/*Objeto para trazar parche NURB */
GLUnurbsObj *curva;

/*Puntos de control */
GLfloat ptosctl[4][3][3] = {
    {{-8.0, -8.0,  8.0}, {-2.8,  0.0,  0.0}, {-8.0, -8.0, -8.0}},
    {{ 1.0,  9.0,  8.0}, {-1.0, -2.0,  0.0}, { 1.0,  9.0, -8.0}},
    {{-1.0,  9.0,  8.0}, { 1.0, -2.0,  0.0}, {-1.0,  9.0, -8.0}},
    {{ 8.0, -8.0,  8.0}, { 2.8,  0.0,  0.0}, { 8.0, -8.0, -8.0}},
};

/*Nodos */
GLfloat nodosu[8] = {0.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 3.0};
GLfloat nodosv[6] = {0.0, 1.0, 1.0, 2.0, 2.0, 3.0};

int main(int argc, char** argv) {
   
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
   glutInitWindowSize (640, 480);
   glutInitWindowPosition (100, 105);
   glutCreateWindow (argv[0]);
   inicializar();
   
   glutDisplayFunc(dibuja);
   glutReshapeFunc(redimensiona);
   glutKeyboardFunc(teclado);
   glutSpecialFunc(tecladoEspecial);
   glutMouseFunc(raton);
   glutIdleFunc(animacion);
   glutMainLoop();
   
   return 0;
}

void inicializar () {
   glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
   glEnable(GL_DEPTH_TEST);
   projection = 0;
   /* posicion inicial de la camara */
   theta = phi = PI / 2.0;

   /* Iluminacion muy basica */
   glEnable(GL_COLOR_MATERIAL);
   glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
   glEnable(GL_LIGHTING);
   glEnable(GL_LIGHT0);

   /* Inicializamo el objeto NURB */
   curva = gluNewNurbsRenderer();
   gluNurbsProperty(curva, GLU_SAMPLING_TOLERANCE, 20.0);

   /* Iniciamos pintando los puntos de control */
   puntosControl = 1;

   /* La curva en solido */
   solid = 1;

   /* Puntotes de control */
   glPointSize(10.0);
   glEnable(GL_POINT_SMOOTH);
   glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
   glEnable(GL_BLEND);
   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}

void redimensiona (int ancho, int alto) {
   
   glViewport (0, 0, ancho, alto);
   
   if (alto == 0){
   	  alto = 1;
   }
   
   aspect = (double) ancho / (double) alto;
   
}

void teclado (unsigned char key, int cx, int cy) {
	
	switch (key) {
		case 27:
		   gluDeleteNurbsRenderer(curva);
		   exit(0);
		break;
		
		case 'P':
		case 'p':
			projection = !projection;
		break;

		case 'S':
		case 's':
			solid = !solid;
		break;

		case 'C':
		case 'c':
			puntosControl = !puntosControl;
		break;


		default: break;
	}
	
	glutPostRedisplay();
}

void tecladoEspecial (int key, int cy, int cx) {
	
	switch (key) {
		//No permitimos caer en los angulos de PI o 0.0 para evitar el GimbalLock
		case GLUT_KEY_UP:
			theta += PI / 10.0;
			if (theta > 9.0 / 10.0 * PI) theta = 9.0 / 10.0 * PI;
		break;

		case GLUT_KEY_DOWN:
			theta -= PI / 10.0;
			if (theta <= PI / 10.0) theta = PI / 10.0;
		break;
		
		case GLUT_KEY_LEFT:
			phi += PI / 10.0;
			if (phi > 2.0 * PI) phi = 2.0 * PI;
		break;
		
		case GLUT_KEY_RIGHT:
			phi -= PI / 10.0;
			if (phi < 0.0) phi = 0.0;
		break;

		default: break;
	}
	
	glutPostRedisplay();
}

void raton (int boton, int estado, int cx, int cy) {
	float x, y;
	
	/* Calculamos las coordenadas de mundo (relativas) apartir de 
	 * las de ventana (absolutas) */
	
	x = ((GLfloat)cx * 2.0 * xMax) / glutGet(GLUT_WINDOW_WIDTH) - xMax;
	y = yMax - ((GLfloat)cy * 2.0 * yMax) / glutGet(GLUT_WINDOW_HEIGHT);
	
	if (boton == GLUT_LEFT_BUTTON && estado == GLUT_DOWN) {
	}
	
	glutPostRedisplay();
}

void animacion (void) {
	glutPostRedisplay();
}	

void dibuja (void) {
	int i, j;
	
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	
	/* Transformacion de proyeccion */
	setProjection();

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	/* Si la proyeccion es en perspectiva, nos movemos un poco atras,
	 * para quedar dentro del volumen de vision (que solo puede ser positivo) */
	if (projection) {
		glTranslatef(0.0, 0.0, -2.0 * MUNDO);
	}
	
	/*Transformación de Vision */
	gluLookAt(MUNDO * cos(phi) * sin(theta), MUNDO * cos(theta), MUNDO * sin(phi) * sin(theta), 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);

	/* Pinta la superficie de NURBS  */
	glColor3f(1.0, 0.0, 0.0);
	gluNurbsProperty(curva, GLU_DISPLAY_MODE, solid ? GLU_FILL : GLU_OUTLINE_POLYGON);

	gluBeginSurface(curva);
		gluNurbsSurface(curva, 8, nodosu, 6, nodosv, 3 * 3, 3, &ptosctl[0][0][0], 4, 3, GL_MAP2_VERTEX_3);
	gluEndSurface(curva);

	/* Pinta los puntos de control */
	if (puntosControl) {
		glColor3f(1.0, 1.0, 0.0);
		glBegin(GL_POINTS);
		for (i = 0; i < 4; i++) {
			for (j = 0; j < 3; j++) {
				glVertex3fv(&ptosctl[i][j][0]);
			}
		}
		glEnd();
	}

	/* Usamos dos buffers por si hay animacion */
	glutSwapBuffers();
}

void setProjection () {
	glMatrixMode (GL_PROJECTION);
	glLoadIdentity ();

	if (projection) { /* Vamos a poner una proyeccion en perspectiva */

		gluPerspective(38.0, aspect, MUNDO, 4.0 * MUNDO);

	} else { /* Vamos a poner una proyeccion ortogonal */

		if (aspect <= 1.0) { /* La ventana es mas alta que ancha */
			xMax = MUNDO;
			yMax = MUNDO / aspect;
		} else { /* La ventana es mas ancha que alta */
			xMax = MUNDO * aspect;
			yMax = MUNDO;
		}

		glOrtho(-xMax, xMax, -yMax, yMax, -2.0 * MUNDO, 2.0 * MUNDO);
	}
}
